Table of Contents
1 環境
本記事の内容は以下環境を前提としています。
- GNU/Linux x86_64
- OpenJDK 64-Bit 1.7.0_xx
2 JVMのスタック領域について
-Xss
、 -XX:ThreadStackSize
パラメータ値と ulimit -s
リソースリミット制限値を混
乱している記事を見受けたため、HotSpotの中身を調べることにしました。
結論を先に、
ulimit -s
のスタック最大サイズ制限値は親プロセスであるJVMランチャーのみ適用される。- JVMランチャーやJavaAPIから起動されたJavaスレッドのスタックサイズは
-Xss
か-XX:ThreadStackSize
の値が適用される。 - JVMランチャーから起動されたイニシャルスレッドのスタックサイズは
-Xss
パラメータの み制御できる、つまり-Xss
の適用範囲は-XX:ThreadStackSize
より広い - JNI経由で外部からJVMにアタッチしたスレッドのスタックサイズはJVMの管理対象外である。
JVMスタックに関して、公式のJVMスペックドキュメント (Java SE 7 Virtual Machine Specification) は次のように記載されています。
2.5.2. Java Virtual Machine Stacks
Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread. A Java Virtual Machine stack stores frames (§2.6). A Java Virtual Machine stack is analogous to the stack of a conventional language such as C: it holds local variables and partial results, and plays a part in method invocation and return. Because the Java Virtual Machine stack is never manipulated directly except to push and pop frames, frames may be heap allocated. The memory for a Java Virtual Machine stack does not need to be contiguous.
※メモ: VMスタック内のFrameはヒープ上に配置されるかも知れない。
-
An implementation of the Java Virtual Machine may use conventional stacks, colloquially called "C stacks," to support native methods (methods written in a language other than the Java programming language). Native method stacks may also be used by the implementation of an interpreter for the Java Virtual Machine's instruction set in a language such as C. Java Virtual Machine implementations that cannot load native methods and that do not themselves rely on conventional stacks need not supply native method stacks. If supplied, native method stacks are typically allocated per thread when each thread is created.
仕様上ではJVMに Java Stack
と Native Stack
2種類のスタックメモリが定義されています。
スタック種別 | 説明 |
---|---|
Java Stack | Javaコード実行時に使われるスタック |
Native Stack | C/C++で書かれた部分実行時に使われるスタック |
システムコール、JNI経由でC/C++ライブラリコール時に使われる |
以下は、JVMのメモリ論理構成イメージです。
+----------------+--------------+-------------+------------------------------------------------------+ | | | | +-----------------+ +----------+ +-------------+ | | Heap | PermGen | Code Cache | | Program Counter | |JavaStack | |Native Stack | | | | | | +-----------------+ | | | | | +----------------+--------------+-------------+ +----------+ +-------------+ | | | Frame #2 | | | | | +----------+ +-------------+ | | << Thread >> | Frame #1 | | | | | +----------+ +-------------+ | +------------------------------------------------------+
図1
理論上は Java Stack
と Native Stack
がスレッド毎に領域が確保されいます。ただし、
実際のメモリページ構成はJDKの実装に依存するものです。
次の情報によると、HotSpotの実装は Java Stack
と Native Stack
が同じメモリ領域を共
有してる。
Troubleshooting Guide for HotSpot VM の 4.1.3 Crash due to Stack Overflow
In the HotSpot implementation, Java methods share stack frames with C/C++ native ★~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ code, namely user native code and the virtual machine itself. Java methods generate code that checks that stack space is available a fixed distance towards the end of the stack so that the native code can be called without exceeding the stack space. This distance towards the end of the stack is called “Shadow Pages.” The size of the shadow pages is between 3 and 20 pages, depending on the platform. This distance is tunable, so that applications with native code needing more than the default distance can increase the shadow page size. The option to increase shadow pages is -XX:StackShadowPages=n, where n is greater than the default stack shadow pages for the platform.
OpenJDKの開発メーリングリストから拾った内容
The stock HotSpot VM (the one in Oracle's Java SE JDK and OpenJDK) uses the same stack for Java and native methods for a Java thread; Java frames and native frames can be mixed together in such a stack. -Xss/-XX:ThreadStackSize controls the whole stack's size for Java threads.
情報源: What the difference between -Xss and -XX:ThreadStackSize is?
3 Javaスレッドのスタックページ構成
HotSpotの実装から見るとJVMから起動されたJavaスレッドのスタックページは次の形で構成さ れると思います。VM内部スレッドやJITコンパイルスレッドのページ構成はまだ別です。
--+-- +------------------------+ | /| |\ | / | StackRedPages | -XX:StackRedPages=1(4Kb) | / | |/ | HotSpot Guard Pages-- +------------------------+ | \ | |\ | \ | StackYellowPages | -XX:StackYellowPages=2(8Kb) | \| |/ | +------------------------+ | /| |\ ★Native Stackはここです★ | / | StackShadowPages | -XX:StackShadowPages=20(80Kb) -XX:ThreadStackSize / | |/ | / +------------------------+ | / | |\ | / | +----------------+ | \ | Normal Stack-- | | Frame | | \ | \ | +----------------+ | \ | \ | | Frame | | ★Java Stackはここです★ | \ | +----------------+ | / | \ | | Frame | | / | \ | +----------------+ | / | \| |/ --+-- +------------------------+
図2
4 HotSpotの実装
以下はHotSpotのソースコードのコメントに書かれたスタックページ構成図です。
jdk7:hotspot/src/os_cpu/linux_x86/vm/os_linux_x86.cpp
// Java thread: // // Low memory addresses // +------------------------+ // | |\ JavaThread created by VM does not have glibc // | glibc guard page | - guard, attached Java thread usually has // | |/ 1 page glibc guard. // P1 +------------------------+ Thread::stack_base() - Thread::stack_size() // | |\ // | HotSpot Guard Pages | - red and yellow pages // | |/ // +------------------------+ JavaThread::stack_yellow_zone_base() // | |\ // | Normal Stack | - // | |/ // P2 +------------------------+ Thread::stack_base() // // Non-Java thread: // // Low memory addresses // +------------------------+ // | |\ // | glibc guard page | - usually 1 page // | |/ // P1 +------------------------+ Thread::stack_base() - Thread::stack_size() // | |\ // | Normal Stack | - // | |/ // P2 +------------------------+ Thread::stack_base() // // ** P1 (aka bottom) and size ( P2 = P1 - size) are the address and stack size returned from // pthread_attr_getstack()
図3
図の内容によるとJavaスレッドと非Javaスレッドのスタックページ構成が異なる。 以下はJVMの非Javaスレッドのリストです。
スレッド名 | 説明 |
---|---|
VM thread | JVM自身のコアスレッド |
Periodic task thread | WatcherThreadのシングルトンインスタンス、定義的なVMタスクを実行する |
GC threads | その名の通りです、メモリ管理自動化役を務める |
Compiler threads | ByteCodeからアセンブラにコンパイルするスレッド |
Signal dispatcher thread | 外部からシグナルをハンドリングする役を務める |
図3の各領域についてソースコードを見ながら解説していきます。
4.1 glibc guard page
glibc guard page
はスタックポインタのオーバーフローを防ぐための-ガードページ。Java
スレッドには HotSpot Guard Pages
が別途用意されているため、この領域のサイズが0であ
る。非Javaスレッドはスタック頂上位置に1ページ分のガードページが割り当てられる。以下は
その実装内容です。
スレッド作成時にglibcの pthread_attr_setguardsize 関数にてガードページを作成してい る
jdk7/hotspot/src/os/linux/vm/os_linux.cpp#l923
// Thread start routine for all newly created threads static void *java_start(Thread *thread) { .............. // glibc guard page pthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type)); .............. }
スレッド種別によってガードページのサイズを決める
jdk7/hotspot/src/os_cpu/linux_x86/vm/os_linux_x86.cpp#l662
size_t os::Linux::default_guard_size(os::ThreadType thr_type) { // Creating guard page is very expensive. Java thread has HotSpot // guard page, only enable glibc guard page for non-Java threads. return (thr_type == java_thread ? 0 : page_size()); }
glibc guard page
の詳細について以下の情報が参考になると思います。
4.2 HotSpot Guard Pages
Javaスレッドスタックオーバーフローを検出するために書き込み不可の HotSpot Guard
Pages
領域がスタック領域のトップ位置に設けている。また HotSpot Guard Pages
は
StackYellowPages
と StackRedPages
から構成されている。
StackYellowPages
はスタックオーバーフローの緩衝域として、余分のメモリーを割り当てま
す。 スタックポインターが StackRedPages
まで行くとStackOverflowErrorが起きる。
以下はページの構成イメージです。
+------------------------+ /| |\ HotSpot / | StackRedPages | - 1ページ (4Kb) Guard / | |/ Pages +------------------------+ \ | |\ \ | StackYellowPages | - 2ページ (8Kb) \| |/ +------------------------+ | | | Normal Stack | | | +------------------------+
図4
Linux/x86_64環境に置いて、 StackYellowPages
と StackRedPages
の初期値が2と1である。
それぞれの値は -XX:StackYellowPages
と -XX:StackRedPages
パラメータにて変更するこ
とが可能です。
下記は HotSpot Guard Pages
の割当処理ロジックです。
Javaスレッド起動時のガードページ割当位置やサイズの計算処理
jdk7:openjdk/hotspot/src/share/vm/runtime/thread.cpp
void JavaThread::create_stack_guard_pages() { if (! os::uses_stack_guard_pages() || _stack_guard_state != stack_guard_unused) return; // ★ ガードページの位置とサイズの計算 address low_addr = stack_base() - stack_size(); size_t len = (StackYellowPages + StackRedPages) * os::vm_page_size(); // ★ ガードページ割当処理はプラットフォーム依存のため、別関数をコール int allocate = os::allocate_stack_guard_pages(); // warning("Guarding at " PTR_FORMAT " for len " SIZE_FORMAT "\n", low_addr, len); if (allocate && !os::create_stack_guard_pages((char *) low_addr, len)) { warning("Attempt to allocate stack guard pages failed."); return; } if (os::guard_memory((char *) low_addr, len)) { _stack_guard_state = stack_guard_enabled; } else { warning("Attempt to protect stack guard pages failed."); if (os::uncommit_memory((char *) low_addr, len)) { warning("Attempt to deallocate stack guard pages failed."); } } }
ガードページ割当処理
jdk7u60:openjdk/hotspot/src/os/linux/vm/os_linux.cpp
bool os::pd_create_stack_guard_pages(char* addr, size_t size) { if (os::Linux::is_initial_thread()) { // As we manually grow stack up to bottom inside create_attached_thread(), // it's likely that os::Linux::initial_thread_stack_bottom is mapped and // we don't need to do anything special. // Check it first, before calling heavy function. uintptr_t stack_extent = (uintptr_t) os::Linux::initial_thread_stack_bottom(); unsigned char vec[1]; if (mincore((address)stack_extent, os::vm_page_size(), vec) == -1) { // Fallback to slow path on all errors, including EAGAIN stack_extent = (uintptr_t) get_stack_commited_bottom( os::Linux::initial_thread_stack_bottom(), (size_t)addr - stack_extent); } if (stack_extent < (uintptr_t)addr) { ::munmap((void*)stack_extent, (uintptr_t)(addr - stack_extent)); } } // ★ここから mmapシステムコールが発行される。 // 最後の引数に書き込み不可のフラグが付与された return os::commit_memory(addr, size, !ExecMem); }
以下は JBoss AS7
アプリケーションサーバ実行時にワーカスレッドのスタック仮想メモリ割
当状況です。
$ cat /proc/`ps -ef | grep [j]boss.modules.system | awk '{print $2}'`/smaps ...省略... 7ff751076000-7ff751079000 ---p 00000000 00:00 0 Size: 12 kB ★説明: StackRedPages(4Kb) + StackYellowPages(8Kb) = 12Kb Rss: 0 kB Pss: 0 kB Shared_Clean: 0 kB Shared_Dirty: 0 kB Private_Clean: 0 kB Private_Dirty: 0 kB Referenced: 0 kB Anonymous: 0 kB AnonHugePages: 0 kB Swap: 0 kB KernelPageSize: 4 kB MMUPageSize: 4 kB Locked: 0 kB VmFlags: mr mw me ac ★説明:書き込み不可 7ff751079000-7ff751177000 rw-p 00000000 00:00 0 [stack:21275] Size: 1016 kB ★説明: ここからNormal Stackページ Rss: 108 kB Pss: 108 kB Shared_Clean: 0 kB Shared_Dirty: 0 kB Private_Clean: 0 kB Private_Dirty: 108 kB Referenced: 108 kB Anonymous: 108 kB AnonHugePages: 0 kB Swap: 0 kB KernelPageSize: 4 kB MMUPageSize: 4 kB Locked: 0 kB VmFlags: rd wr mr mw me ac ...省略...
次のSystemTapスクリプトで HotSpot Guard Pages
の割当処理をトレースしてみた。
jvm_memory_trace.stp
#!/usr/bin/stap -p4 probe process("/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so").function("commit_memory_impl") { printf("---------------------------------------------------------\n") printf("%d\t%s\n", tid(), $$parms) print_ustack(ubacktrace()) }
出力結果
|$ stap jvm_memory_trace.stp -c "java -version" |Using a compile server. |WARNING: Missing unwind data for module, rerun with 'stap -d ...dk-1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/jli/libjli.so' |WARNING: Missing unwind data for module, rerun with 'stap -d /usr/lib64/libpthread-2.17.so' |java version "1.7.0_75" |OpenJDK Runtime Environment (rhel-2.5.4.7.el7_1-x86_64 u75-b13) |OpenJDK 64-Bit Server VM (build 24.75-b04, mixed mode) | |★省略★ |-------------------------------------------------------------------------------------------------------------- ①|12179 exec=0x0 size=0x3000 addr=0x7f1d05b57000 ★commit_memory_impl関数実行時の引数情報 | 0x7f1d04808371 : _ZN2os16pd_commit_memoryEPcmb+0x1/0xf0 [...1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so] | 0x7f1d04802dee : _ZN2os13commit_memoryEPcmb+0x2e/0xd0 [...1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so] ②| 0x7f1d048092df : _ZN2os27pd_create_stack_guard_pagesEPcm+0x7f/0x180 [...1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so] | 0x7f1d04945519 : _ZN7Threads9create_vmEP14JavaVMInitArgsPb+0x339/0x1550 [...1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so] | 0x7f1d0463fca7 : JNI_CreateJavaVM+0x67/0x2a0 [...1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so] | 0x7f1d0562aa68 : 0x7f1d0562aa68 [...dk-1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/jli/libjli.so+0x2a68/0x20e000] |-------------------------------------------------------------------------------------------------------------- |★以降省略★
出力結果に①の commit_memory_impl 仮想メモリ割当処理の第2引数にメモリサイズを指定し
ています。 size=0x3000
の値が16進数ですので、10進数に変換すると12Kbです。予測通りで
すね。
gdbを用いて上記出力結果から②のソースコード位置を特定する方法を以下に示す。
$ gdb /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-64.el7 ★一部内容省略★ Reading symbols from /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so...Reading symbols from /usr/lib/debug/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.75-2.5.4.7.el7_1.x86_64/jre/lib/amd64/server/libjvm.so.debug...done. done. ★出力結果から関数名ぽいの文字列で関数を探す (gdb) info functions pd_create_stack_guard_pages All functions matching regular expression "pd_create_stack_guard_pages": ★検索結果 File /usr/src/debug/java-1.7.0-openjdk-1.7.0.75-2.5.4.7.el7_1.x86_64/openjdk/hotspot/src/os/linux/vm/os_linux.cpp: bool os::pd_create_stack_guard_pages(char*, unsigned long); (gdb)
4.3 Normal Stack
Javaスレッドを前提に置いて、 Normal Stack
には通常Javaメソッド実行時のフレーム情報
が格納される。ただし、スレッドからC/C++メソッドを実行する時も Normal Stack
が利用さ
れる。スタックのトップ位置にC/C++メソッド実行用の StackShadowPages
が設けられている。
Linux/x86_64環境に置いて StackShadowPages
の初期値が20である。
+------------------------+ /| |\ / | StackShadowPages | -XX:StackShadowPages=20(80Kb) / | |/ ★Native Stackはここです! / +------------------------+ / | |\ / | +----------------+ | \ Normal Stack | | Frame | | \ \ | +----------------+ | \ \ | | Frame | | - ★Java Stackはここです! \ | +----------------+ | / \ | | Frame | | / \ | +----------------+ | / \| |/ +------------------------+
図5
以下は StackShadowPages
初期値の代入処理ロジックです。
jdk7:hotspot/src/cpu/x86/vm/globals_x86.hpp#l60
#ifdef AMD64 // Very large C++ stack frames using solaris-amd64 optimized builds // due to lack of optimization caused by C++ compiler bugs define_pd_global(intx, StackShadowPages, NOT_WIN64(20) WIN64_ONLY(6) DEBUG_ONLY(+2)); #else define_pd_global(intx, StackShadowPages, 6 DEBUG_ONLY(+5)); #endif // AMD64
StackShadowPages
に関して以下の情報が参考になると思います。
- Troubleshooting Guide for HotSpot VM 4.1.3 Crash due to Stack Overflow
- JDK-7059899 : Stack overflows in Java code cause 64-bit JVMs to exit due to SIGSEGV
What does the StackShadowPages JVM setting do?
StackShadowPages reserves a portion of the thread stack for native layer allocations. The page size usually is 4096b, which mean that 20 pages would occupy 80Kb. The thread stack is sized through -Xss. Consider some config examples: -Xss1024k -XX:StackShadowPages=10 [ 984kb java stack | 40kb native stack] -Xss1024k -XX:StackShadowPages=20 [ 944kb java stack | 80kb native stack] -Xss512k -XX:StackShadowPages=10 [ 472kb java stack | 40kb native stack] If you decrease just -Xss, the overall stack is decreased, but the StackShadowPages native reservation is not; only the java portion would lose space. Likewise if you only increase -Xss, only the java portion gains space with the increased stack. If you increase StackShadowPages, the java portion becomes smaller so that the native portion can be larger. If the native portion of a stack is exhausted, the JVM can fatally crash so sometimes StackShadowPages needs to be increased.
5 スタックサイズの制御
従いましてJava Methodに使えるスタック領域(Java Stack)のサイズは次の式で計算出来る。
Java Satck Size = Thread::stack_size() - ((StackRedPages + StackYellowPages + StackShadowPages) * PageSize)
Thread::stack_size()
の値はスレッド起動時にglibcの pthread_attr_setstacksize 関数を
用いて設定される。以下はHotSpotの実装です。
JVM起動時に実行される処理 jdk7/hotspot/src/os/linux/vm/os_linux.cpp#4820
// this is called _after_ the global arguments have been parsed jint os::init_2(void) { // ★一部省略★ // ★スレッドに割当るスタックサイズの最小許容値の計算 // Check minimum allowable stack size for thread creation and to initialize // the java system classes, including StackOverflowError - depends on page // size. Add a page for compiler2 recursion in main thread. // Add in 2*BytesPerWord times page size to account for VM stack during // class initialization depending on 32 or 64 bit VM. os::Linux::min_stack_allowed = MAX2(os::Linux::min_stack_allowed, (size_t)(StackYellowPages+StackRedPages+StackShadowPages) * Linux::page_size() + (2*BytesPerWord COMPILER2_PRESENT(+1)) * Linux::vm_default_page_size()); #ifdef ZERO // If this is Zero, allow at the very minimum one page each for the // Zero stack and the native stack. This won't make any difference // for 4k pages, but is significant for large pages. os::Linux::min_stack_allowed = MAX2(os::Linux::min_stack_allowed, (size_t)(StackYellowPages+StackRedPages+StackShadowPages+2) * Linux::page_size()); #endif size_t threadStackSizeInBytes = ThreadStackSize * K; if (threadStackSizeInBytes != 0 && threadStackSizeInBytes < os::Linux::min_stack_allowed) { tty->print_cr("\nThe stack size specified is too small, " "Specify at least %dk", os::Linux::min_stack_allowed/ K); return JNI_ERR; } // ★-XX:ThreadStackSizeの値を静的_stack_size_at_create変数に代入する // Make the stack size a multiple of the page size so that // the yellow/red zones can be guarded. JavaThread::set_stack_size_at_create(round_to(threadStackSizeInBytes, vm_page_size())); // ★イニシャルスレッドのスタックサイズ設定処理(★TODO: 別途調査する) Linux::capture_initial_stack(JavaThread::stack_size_at_create()); // ★以降省略★
新規スレッド起動時の処理 jdk7:hotspot/src/os/linux/vm/os_linux.cpp#901
bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) { // ★一部省略★ // ★スレッド種別毎にスタックサイズを決める if (os::Linux::supports_variable_stack_size()) { // calculate stack size if it's not specified by caller if (stack_size == 0) { stack_size = os::Linux::default_stack_size(thr_type); switch (thr_type) { //★Javaワーカスレッドの場合 case os::java_thread: // Java threads use ThreadStackSize which default value can be // changed with the flag -Xss assert (JavaThread::stack_size_at_create() > 0, "this should be set"); stack_size = JavaThread::stack_size_at_create(); break; //★JITコンパイラスレッドの場合 case os::compiler_thread: if (CompilerThreadStackSize > 0) { stack_size = (size_t)(CompilerThreadStackSize * K); break; } // else fall through: // use VMThreadStackSize if CompilerThreadStackSize is not defined //★VMスレッド、GCスレッド、ウォッチャースレッドの場合 case os::vm_thread: case os::pgc_thread: case os::cgc_thread: case os::watcher_thread: if (VMThreadStackSize > 0) stack_size = (size_t)(VMThreadStackSize * K); break; } } stack_size = MAX2(stack_size, os::Linux::min_stack_allowed); // ★glic関数用いてstack領域を確保する pthread_attr_setstacksize(&attr, stack_size); // ★以降省略★
以上コードの通り、JVMから起動されたスレッドの種別毎のスタックサイズが下記XXパラメータ値が適用されてい る。
引数 | Linux/x86_64環境初期値 | 適用範囲 |
---|---|---|
-XX:ThreadStackSize | 1M | Javaスレッド |
-XX:VMThreadStackSize | 1M | VM thread、GC threads、VM Periodic Task Threadなど |
-XX:CompilerThreadStackSize | 4M | C1 C2 CompilerThread |
以下は実機にて確認された各パラメータの初期値です。
$ java -XX:+PrintFlagsFinal -version | grep -e "CompilerThreadStackSize\|ThreadStackSize\|VMThreadStackSize" intx CompilerThreadStackSize = 0 {pd product} intx ThreadStackSize = 1024 {pd product} intx VMThreadStackSize = 1024 {pd product} java version "1.7.0_75" OpenJDK Runtime Environment (rhel-2.5.4.7.el7_1-x86_64 u75-b13) OpenJDK 64-Bit Server VM (build 24.75-b04, mixed mode)
CompilerThreadStackSize
が指定しない場合下記コードにて初期値が代入される。
jdk7:hotspot/src/os_cpu/linux_x86/vm/os_linux_x86.cpp#652
// return default stack size for thr_type size_t os::Linux::default_stack_size(os::ThreadType thr_type) { // default stack size (compiler thread needs larger stack) #ifdef AMD64 size_t s = (thr_type == os::compiler_thread ? 4 * M : 1 * M); #else size_t s = (thr_type == os::compiler_thread ? 2 * M : 512 * K); #endif // AMD64 return s; }
次のサンプルプログラムを実行して、各スレッドのスタックサイズ値を実測してみる。
HelloWorld.java
public class HelloWorld implements Runnable { public void run(){ while(true) { try { Thread.sleep(1 * 1000L); System.out.println(Thread.currentThread().toString() + ": Hello World"); } catch (Exception e) { } } } public static void main(String[] args) throws Exception { Thread[] t_arry = new Thread[5]; for (int i = 0; i < t_arry.length; i++) { t_arry[i] = new Thread(new HelloWorld()); t_arry[i].start(); } for (int i = 0; i < t_arry.length; i++) { t_arry[i].join(); } } }
スタックサイズの実測値は次のスクリプトで取得しています。
jvm_stacksize.sh
#!/bin/sh if [ ! $# -eq 1 ]; then echo "Usage ${0} <JVM PID>" exit 1 fi printf "[ PID ]\t[StackSize]\t[GuardPages]\t[Thread Name]\n" # jstackの出力結果からスレッドIDと名前を抽出する jstack $1 | grep nid | sed -e "s/^\"\(.*\)\".*nid=\(0x[0-9|a-z]*\).*$/\2,\1/" | sort | while read line do # スレッドIDを切り出す pid_hex=`echo "${line}" | awk -F"," '{print $1}'` # スレッド名を切り出す thread_name=`echo "${line}" | awk -F"," '{print $2}'` # スレッドIDを10進数に変換 pid=`printf '%d\n' ${pid_hex}` # /proc/<pid>/smaps ファイルからスタックサイズ、ガードページサイズを取得する guard_page=`cat /proc/$1/smaps | grep -B15 "stack:${pid}"| head -1 | awk '{print $2}'` stack_page=`cat /proc/$1/smaps | grep -A1 "stack:${pid}" | tail -1 | awk '{print $2}'` stack_size=`expr ${guard_page} + ${stack_page}` printf "%7d\t%11s\t%12s\t%s\n" "${pid}" "${stack_size}Kb" "${guard_page}Kb" "${thread_name}" done
スタックサイズを明示的に指定して、サンプルを実行する。
- -XX:ThreadStackSize=512
- -XX:VMThreadStackSize: 2048
- -XX:CompilerThreadStackSize=3072
java -XX:VMThreadStackSize=2048 -XX:CompilerThreadStackSize=3072 -XX:ThreadStackSize=512 HelloWorld Thread[Thread-0,5,main]: Hello World Thread[Thread-4,5,main]: Hello World Thread[Thread-1,5,main]: Hello World Thread[Thread-3,5,main]: Hello World Thread[Thread-2,5,main]: Hello World ★以降は省略
測定結果
$ ./jvm_stacksize.sh `ps -ef | grep "[j]ava.*HelloWorld" | awk '{print $2}'` [ PID ] [StackSize] [GuardPages] [Thread Name] 17285 1048Kb 12Kb main 17286 2096Kb 4Kb GC task thread#0 (ParallelGC) 17287 2052Kb 4Kb GC task thread#1 (ParallelGC) 17288 2052Kb 4Kb GC task thread#2 (ParallelGC) 17289 2052Kb 4Kb GC task thread#3 (ParallelGC) 17290 2052Kb 4Kb VM Thread 17291 64584Kb 12Kb Reference Handler 17292 516Kb 12Kb Finalizer 17293 516Kb 12Kb Signal Dispatcher 17294 3076Kb 12Kb C2 CompilerThread0 17295 3076Kb 12Kb C2 CompilerThread1 17296 516Kb 12Kb Service Thread 17297 2052Kb 4Kb VM Periodic Task Thread 17298 516Kb 12Kb Thread-0 17299 516Kb 12Kb Thread-1 17300 516Kb 12Kb Thread-2 17301 516Kb 12Kb Thread-3 17302 516Kb 12Kb Thread-4 17342 516Kb 12Kb Attach Listener
実測値はXXパラメータで指定した値より1ページ分多い。これは glibc
内部の
allocate_stack 処理で追加されているものです。
6 ulimit -sの影響範囲
Linux環境に置いて、JVM内のスレッドは全てglibcの pthread_create
関数経由で起動される。
スレッド起動時にスタックサイズ明示的に指定していない場合、 ulimit -s
で設定された値
がスタックのデフォルトサイズとして適用される。前文に書いた通りJVMはスレッド起動時に明
示的 pthread_attr_setstacksize
関数でXXパラメータ値の元にスタックサイズを指定してい
るため、これらのスレッドのスタックサイズは ulimit -s
の値に影響されないだ。
ただし、JVMランチャー自身は ulimit -s
の制限値が適用される。
ulimit
コマンドでスタックの上限値 RLIMIT_STACK
を64Kbを設定し、サンプルプログラム
グライムを実行すると、ランチャーのスタックサイズが60Kbで収まった。
$ ulimit -s 64 $ java -Xss1024K -XX:VMThreadStackSize=2048 -XX:CompilerThreadStackSize=3072 -XX:ThreadStackSize=512 HelloWorld Thread[Thread-1,5,main]: Hello World Thread[Thread-3,5,main]: Hello World Thread[Thread-0,5,main]: Hello World Thread[Thread-2,5,main]: Hello World Thread[Thread-4,5,main]: Hello World ★省略
pmap
コマンドで仮想メモリマップの最上位アドレス近くにランチャーのスタックサイズを確
認することができる。
$ pmap `ps -ef | grep "[j]ava.*HelloWorld" | awk '{print $2}'` ★省略 00007f39d068d000 4K r---- ld-2.17.so 00007f39d068e000 4K rw--- ld-2.17.so 00007f39d068f000 4K rw--- [ anon ] 00007fff7cf79000 60K rw--- [ stack ] ★ランチャーのスタックサイズ 00007fff7cffe000 8K r-x-- [ anon ] ffffffffff600000 4K r-x-- [ anon ] total 3513684K
JVM内の各スレッドのスタックサイズは下記の通りです、 RLIMIT_STACK
に影響されていない
ことが分かります。
]$ ./jvm_stacksize.sh `ps -ef | grep "[j]ava.*HelloWorld" | awk '{print $2}'` [ PID ] [StackSize] [GuardPages] [Thread Name] 10770 1048Kb 12Kb main 10771 2096Kb 4Kb GC task thread#0 (ParallelGC) 10772 2052Kb 4Kb GC task thread#1 (ParallelGC) 10773 2052Kb 4Kb GC task thread#2 (ParallelGC) 10774 2052Kb 4Kb GC task thread#3 (ParallelGC) 10775 15812Kb 4Kb VM Thread 10776 516Kb 12Kb Reference Handler 10777 63556Kb 12Kb Finalizer 10778 516Kb 12Kb Signal Dispatcher 10779 3076Kb 12Kb C2 CompilerThread0 10780 3076Kb 12Kb C2 CompilerThread1 10781 516Kb 12Kb Service Thread 10782 2052Kb 4Kb VM Periodic Task Thread 10783 516Kb 12Kb Thread-0 10784 516Kb 12Kb Thread-1 10785 516Kb 12Kb Thread-2 10786 516Kb 12Kb Thread-3 10787 516Kb 12Kb Thread-4 11203 516Kb 12Kb Attach Listener
7 -Xssと-XX:ThreadStackSizeの違い
-Xss
と -XX:ThreadStackSize
両方ともJavaスレッドのスタックを指定するパラメータで
ある。 ただし、JVMランチャーから起動されたイニシャルスレッドのスタックサイズの制御は
-Xss
パラメータのみできる。
以下はJVMランチャーからイニシャルスレッド起動するまでの流れ
行 | ★ランチャーの実行 1| openjdk/jdk/src/share/bin/main.c:93 ==> int main(int, char **); 2| openjdk/jdk/src/share/bin/java.c:170 ==> int JLI_Launch(int, char **, int, const char **, int, const char **, const char *, const char *, const char *, const char *, jboolean, jboolean, jboolean, jint); 3| openjdk/jdk/src/share/bin/java.c:1835 ==> int ContinueInNewThread(InvocationFunctions *, jlong, int, char **, int, char *, int); | ★イニシャルスレッド起動 4| openjdk/jdk/src/solaris/bin/java_md_solinux.c:1021 ==> int ContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) 5| openjdk/jdk/src/share/bin/java.c:337 ==> int JavaMain(void *); | openjdk/jdk/src/share/bin/java.c:1097 ==> jboolean InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn) 6| openjdk/hotspot/src/share/vm/prims/jni.cpp ==> jint JNI_CreateJavaVM(JavaVM**, void**, void*); 7| openjdk/hotspot/src/share/vm/runtime/thread.cpp:3271 ==> jint Threads::create_vm(JavaVMInitArgs*, bool*); 8| openjdk/hotspot/src/os/linux/vm/os_linux.cpp:4898 ==> jint os::init_2(void) 9| openjdk/hotspot/src/os/linux/vm/os_linux.cpp:1205 ==> void os::Linux::capture_initial_stack(size_t max_size)
- 行1: JVMランチャーのmain関数
- 行2:
JLI_Launch
関数にてコマンドラインパラメータのパーシング処理が実行される。 - 行3:
-Xss
パラメータが指定されていない場合、デフォルト値(1024Kb)を取得し(4)に渡す。 - 行4: glibcの
pthread_create
関数を用いてイニシャルスレッドを起動する。-Xss
の 値がスタックサイズに適用される。
-Xss
と -XX:ThreadStackSize
片方指定する場合、と両方指定する場合効果が違うので要注意です。
イニシャルスレッド | ワーカスレッド | |
---|---|---|
-Xss2048K | 2048K | 2048K |
-XX:ThreadStackSize=2048 | 1024K | 2048K |
-Xss2048K | 2048K | 512K |
-XX:ThreadStackSize=512 |
8 参考
本記事書く際に下記コンテンツを参考した。
- What the difference between -Xss and -XX:ThreadStackSize is?
- Troubleshooting Guide for HotSpot VM
- HotSpot Runtime Overview
- JDK 8: Thread Stack Size Tuning
- Java Runtime: повседневные обязанности
- Java from the trenches: improving reliability
- Recommended JVM Tuning Set
- where is the Java stack allocated?
- How does stack allocation work in Linux?
- How Memory Allocation Affects Performance in Multithreaded Programs
- What do the different (HotSpot) JVM thread types do?
- What is thread stack size option(-Xss) given to jvm? Why does it have a limit of atleast 68k in a windows pc?
- Mapping java thread to pstack and pmap
- JVM のメモリ構造
- pthreadについて(スタックサイズ)
- Linux の pthread のデフォルトスタックサイズについて
- PTHREAD_ATTR_SETSTACKSIZE
- マルチスレッドのプログラミング - スタックについて
- {OS} 仮想メモリ空間のメモリマップを調べる
- VSS RSS PSS USS の説明
- 咨询各位大神,使用jni,当栈内空间使用为1M时,会触发core